3 * @defgroup Language Language
9 if( !defined( 'MEDIAWIKI' ) ) {
10 echo "This file is part of MediaWiki, it is not a valid entry point.\n";
15 global $wgLanguageNames;
16 require_once( dirname(__FILE__
) . '/Names.php' ) ;
18 global $wgInputEncoding, $wgOutputEncoding;
21 * These are always UTF-8, they exist only for backwards compatibility
23 $wgInputEncoding = "UTF-8";
24 $wgOutputEncoding = "UTF-8";
26 if( function_exists( 'mb_strtoupper' ) ) {
27 mb_internal_encoding('UTF-8');
31 * a fake language converter
37 function FakeConverter($langobj) {$this->mLang
= $langobj;}
38 function autoConvertToAllVariants($text) {return $text;}
39 function convert($t, $i) {return $t;}
40 function parserConvert($t, $p) {return $t;}
41 function getVariants() { return array( $this->mLang
->getCode() ); }
42 function getPreferredVariant() {return $this->mLang
->getCode(); }
43 function findVariantLink(&$l, &$n, $ignoreOtherCond = false) {}
44 function getExtraHashOptions() {return '';}
45 function getParsedTitle() {return '';}
46 function markNoConversion($text, $noParse=false) {return $text;}
47 function convertCategoryKey( $key ) {return $key; }
48 function convertLinkToAllVariants($text){ return array( $this->mLang
->getCode() => $text); }
49 function armourMath($text){ return $text; }
53 * Internationalisation code
57 var $mConverter, $mVariants, $mCode, $mLoaded = false;
58 var $mMagicExtensions = array(), $mMagicHookDone = false;
60 var $mNamespaceIds, $namespaceNames, $namespaceAliases;
61 var $dateFormatStrings = array();
63 var $mExtendedSpecialPageAliases;
65 static public $dataCache;
66 static public $mLangObjCache = array();
68 static public $mWeekdayMsgs = array(
69 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
73 static public $mWeekdayAbbrevMsgs = array(
74 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
77 static public $mMonthMsgs = array(
78 'january', 'february', 'march', 'april', 'may_long', 'june',
79 'july', 'august', 'september', 'october', 'november',
82 static public $mMonthGenMsgs = array(
83 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
84 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
87 static public $mMonthAbbrevMsgs = array(
88 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
89 'sep', 'oct', 'nov', 'dec'
92 static public $mIranianCalendarMonthMsgs = array(
93 'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
94 'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
95 'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
96 'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
99 static public $mHebrewCalendarMonthMsgs = array(
100 'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
101 'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
102 'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
103 'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
104 'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
107 static public $mHebrewCalendarMonthGenMsgs = array(
108 'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
109 'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
110 'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
111 'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
112 'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
115 static public $mHijriCalendarMonthMsgs = array(
116 'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
117 'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
118 'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
119 'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
123 * Get a cached language object for a given language code
125 static function factory( $code ) {
126 if ( !isset( self
::$mLangObjCache[$code] ) ) {
127 if( count( self
::$mLangObjCache ) > 10 ) {
128 // Don't keep a billion objects around, that's stupid.
129 self
::$mLangObjCache = array();
131 self
::$mLangObjCache[$code] = self
::newFromCode( $code );
133 return self
::$mLangObjCache[$code];
137 * Create a language object for a given language code
139 protected static function newFromCode( $code ) {
141 static $recursionLevel = 0;
142 if ( $code == 'en' ) {
145 $class = 'Language' . str_replace( '-', '_', ucfirst( $code ) );
146 // Preload base classes to work around APC/PHP5 bug
147 if ( file_exists( "$IP/languages/classes/$class.deps.php" ) ) {
148 include_once("$IP/languages/classes/$class.deps.php");
150 if ( file_exists( "$IP/languages/classes/$class.php" ) ) {
151 include_once("$IP/languages/classes/$class.php");
155 if ( $recursionLevel > 5 ) {
156 throw new MWException( "Language fallback loop detected when creating class $class\n" );
159 if( ! class_exists( $class ) ) {
160 $fallback = Language
::getFallbackFor( $code );
162 $lang = Language
::newFromCode( $fallback );
164 $lang->setCode( $code );
171 public static function getLocalisationCache() {
172 if ( is_null( self
::$dataCache ) ) {
173 global $wgLocalisationCacheConf;
174 $class = $wgLocalisationCacheConf['class'];
175 self
::$dataCache = new $class( $wgLocalisationCacheConf );
177 return self
::$dataCache;
180 function __construct() {
181 $this->mConverter
= new FakeConverter($this);
182 // Set the code to the name of the descendant
183 if ( get_class( $this ) == 'Language' ) {
186 $this->mCode
= str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) );
188 self
::getLocalisationCache();
192 * Reduce memory usage
194 function __destruct() {
195 foreach ( $this as $name => $value ) {
196 unset( $this->$name );
201 * Hook which will be called if this is the content language.
202 * Descendants can use this to register hook functions or modify globals
204 function initContLang() {}
207 * @deprecated Use User::getDefaultOptions()
210 function getDefaultUserOptions() {
211 wfDeprecated( __METHOD__
);
212 return User
::getDefaultOptions();
215 function getFallbackLanguageCode() {
216 if ( $this->mCode
=== 'en' ) {
219 return self
::$dataCache->getItem( $this->mCode
, 'fallback' );
224 * Exports $wgBookstoreListEn
227 function getBookstoreList() {
228 return self
::$dataCache->getItem( $this->mCode
, 'bookstoreList' );
234 function getNamespaces() {
235 if ( is_null( $this->namespaceNames
) ) {
236 global $wgExtraNamespaces, $wgMetaNamespace, $wgMetaNamespaceTalk;
238 $this->namespaceNames
= self
::$dataCache->getItem( $this->mCode
, 'namespaceNames' );
239 if ( $wgExtraNamespaces ) {
240 $this->namespaceNames
= $wgExtraNamespaces +
$this->namespaceNames
;
243 $this->namespaceNames
[NS_PROJECT
] = $wgMetaNamespace;
244 if ( $wgMetaNamespaceTalk ) {
245 $this->namespaceNames
[NS_PROJECT_TALK
] = $wgMetaNamespaceTalk;
247 $talk = $this->namespaceNames
[NS_PROJECT_TALK
];
248 $this->namespaceNames
[NS_PROJECT_TALK
] =
249 $this->fixVariableInNamespace( $talk );
252 # The above mixing may leave namespaces out of canonical order.
253 # Re-order by namespace ID number...
254 ksort( $this->namespaceNames
);
256 return $this->namespaceNames
;
260 * A convenience function that returns the same thing as
261 * getNamespaces() except with the array values changed to ' '
262 * where it found '_', useful for producing output to be displayed
263 * e.g. in <select> forms.
267 function getFormattedNamespaces() {
268 $ns = $this->getNamespaces();
269 foreach($ns as $k => $v) {
270 $ns[$k] = strtr($v, '_', ' ');
276 * Get a namespace value by key
278 * $mw_ns = $wgContLang->getNsText( NS_MEDIAWIKI );
279 * echo $mw_ns; // prints 'MediaWiki'
282 * @param $index Int: the array key of the namespace to return
283 * @return mixed, string if the namespace value exists, otherwise false
285 function getNsText( $index ) {
286 $ns = $this->getNamespaces();
287 return isset( $ns[$index] ) ?
$ns[$index] : false;
291 * A convenience function that returns the same thing as
292 * getNsText() except with '_' changed to ' ', useful for
297 function getFormattedNsText( $index ) {
298 $ns = $this->getNsText( $index );
299 return strtr($ns, '_', ' ');
303 * Get a namespace key by value, case insensitive.
304 * Only matches namespace names for the current language, not the
305 * canonical ones defined in Namespace.php.
307 * @param $text String
308 * @return mixed An integer if $text is a valid value otherwise false
310 function getLocalNsIndex( $text ) {
311 $lctext = $this->lc($text);
312 $ids = $this->getNamespaceIds();
313 return isset( $ids[$lctext] ) ?
$ids[$lctext] : false;
316 function getNamespaceAliases() {
317 if ( is_null( $this->namespaceAliases
) ) {
318 $aliases = self
::$dataCache->getItem( $this->mCode
, 'namespaceAliases' );
322 foreach ( $aliases as $name => $index ) {
323 if ( $index === NS_PROJECT_TALK
) {
324 unset( $aliases[$name] );
325 $name = $this->fixVariableInNamespace( $name );
326 $aliases[$name] = $index;
330 $this->namespaceAliases
= $aliases;
332 return $this->namespaceAliases
;
335 function getNamespaceIds() {
336 if ( is_null( $this->mNamespaceIds
) ) {
337 global $wgNamespaceAliases;
338 # Put namespace names and aliases into a hashtable.
339 # If this is too slow, then we should arrange it so that it is done
340 # before caching. The catch is that at pre-cache time, the above
341 # class-specific fixup hasn't been done.
342 $this->mNamespaceIds
= array();
343 foreach ( $this->getNamespaces() as $index => $name ) {
344 $this->mNamespaceIds
[$this->lc($name)] = $index;
346 foreach ( $this->getNamespaceAliases() as $name => $index ) {
347 $this->mNamespaceIds
[$this->lc($name)] = $index;
349 if ( $wgNamespaceAliases ) {
350 foreach ( $wgNamespaceAliases as $name => $index ) {
351 $this->mNamespaceIds
[$this->lc($name)] = $index;
355 return $this->mNamespaceIds
;
360 * Get a namespace key by value, case insensitive. Canonical namespace
361 * names override custom ones defined for the current language.
363 * @param $text String
364 * @return mixed An integer if $text is a valid value otherwise false
366 function getNsIndex( $text ) {
367 $lctext = $this->lc($text);
368 if ( ( $ns = MWNamespace
::getCanonicalIndex( $lctext ) ) !== null ) {
371 $ids = $this->getNamespaceIds();
372 return isset( $ids[$lctext] ) ?
$ids[$lctext] : false;
376 * short names for language variants used for language conversion links.
378 * @param $code String
381 function getVariantname( $code ) {
382 return $this->getMessageFromDB( "variantname-$code" );
385 function specialPage( $name ) {
386 $aliases = $this->getSpecialPageAliases();
387 if ( isset( $aliases[$name][0] ) ) {
388 $name = $aliases[$name][0];
390 return $this->getNsText( NS_SPECIAL
) . ':' . $name;
393 function getQuickbarSettings() {
395 $this->getMessage( 'qbsettings-none' ),
396 $this->getMessage( 'qbsettings-fixedleft' ),
397 $this->getMessage( 'qbsettings-fixedright' ),
398 $this->getMessage( 'qbsettings-floatingleft' ),
399 $this->getMessage( 'qbsettings-floatingright' )
403 function getMathNames() {
404 return self
::$dataCache->getItem( $this->mCode
, 'mathNames' );
407 function getDatePreferences() {
408 return self
::$dataCache->getItem( $this->mCode
, 'datePreferences' );
411 function getDateFormats() {
412 return self
::$dataCache->getItem( $this->mCode
, 'dateFormats' );
415 function getDefaultDateFormat() {
416 $df = self
::$dataCache->getItem( $this->mCode
, 'defaultDateFormat' );
417 if ( $df === 'dmy or mdy' ) {
418 global $wgAmericanDates;
419 return $wgAmericanDates ?
'mdy' : 'dmy';
425 function getDatePreferenceMigrationMap() {
426 return self
::$dataCache->getItem( $this->mCode
, 'datePreferenceMigrationMap' );
429 function getImageFile( $image ) {
430 return self
::$dataCache->getSubitem( $this->mCode
, 'imageFiles', $image );
433 function getDefaultUserOptionOverrides() {
434 return self
::$dataCache->getItem( $this->mCode
, 'defaultUserOptionOverrides' );
437 function getExtraUserToggles() {
438 return self
::$dataCache->getItem( $this->mCode
, 'extraUserToggles' );
441 function getUserToggle( $tog ) {
442 return $this->getMessageFromDB( "tog-$tog" );
446 * Get language names, indexed by code.
447 * If $customisedOnly is true, only returns codes with a messages file
449 public static function getLanguageNames( $customisedOnly = false ) {
450 global $wgLanguageNames, $wgExtraLanguageNames;
451 $allNames = $wgExtraLanguageNames +
$wgLanguageNames;
452 if ( !$customisedOnly ) {
458 $dir = opendir( "$IP/languages/messages" );
459 while( false !== ( $file = readdir( $dir ) ) ) {
461 if( preg_match( '/Messages([A-Z][a-z_]+)\.php$/', $file, $m ) ) {
462 $code = str_replace( '_', '-', strtolower( $m[1] ) );
463 if ( isset( $allNames[$code] ) ) {
464 $names[$code] = $allNames[$code];
473 * Get a message from the MediaWiki namespace.
475 * @param $msg String: message name
478 function getMessageFromDB( $msg ) {
479 return wfMsgExt( $msg, array( 'parsemag', 'language' => $this ) );
482 function getLanguageName( $code ) {
483 $names = self
::getLanguageNames();
484 if ( !array_key_exists( $code, $names ) ) {
487 return $names[$code];
490 function getMonthName( $key ) {
491 return $this->getMessageFromDB( self
::$mMonthMsgs[$key-1] );
494 function getMonthNameGen( $key ) {
495 return $this->getMessageFromDB( self
::$mMonthGenMsgs[$key-1] );
498 function getMonthAbbreviation( $key ) {
499 return $this->getMessageFromDB( self
::$mMonthAbbrevMsgs[$key-1] );
502 function getWeekdayName( $key ) {
503 return $this->getMessageFromDB( self
::$mWeekdayMsgs[$key-1] );
506 function getWeekdayAbbreviation( $key ) {
507 return $this->getMessageFromDB( self
::$mWeekdayAbbrevMsgs[$key-1] );
510 function getIranianCalendarMonthName( $key ) {
511 return $this->getMessageFromDB( self
::$mIranianCalendarMonthMsgs[$key-1] );
514 function getHebrewCalendarMonthName( $key ) {
515 return $this->getMessageFromDB( self
::$mHebrewCalendarMonthMsgs[$key-1] );
518 function getHebrewCalendarMonthNameGen( $key ) {
519 return $this->getMessageFromDB( self
::$mHebrewCalendarMonthGenMsgs[$key-1] );
522 function getHijriCalendarMonthName( $key ) {
523 return $this->getMessageFromDB( self
::$mHijriCalendarMonthMsgs[$key-1] );
527 * Used by date() and time() to adjust the time output.
529 * @param $ts Int the time in date('YmdHis') format
530 * @param $tz Mixed: adjust the time by this amount (default false, mean we
531 * get user timecorrection setting)
534 function userAdjust( $ts, $tz = false ) {
535 global $wgUser, $wgLocalTZoffset;
537 if ( $tz === false ) {
538 $tz = $wgUser->getOption( 'timecorrection' );
541 $data = explode( '|', $tz, 3 );
543 if ( $data[0] == 'ZoneInfo' ) {
544 if ( function_exists( 'timezone_open' ) && @timezone_open
( $data[2] ) !== false ) {
545 $date = date_create( $ts, timezone_open( 'UTC' ) );
546 date_timezone_set( $date, timezone_open( $data[2] ) );
547 $date = date_format( $date, 'YmdHis' );
550 # Unrecognized timezone, default to 'Offset' with the stored offset.
555 if ( $data[0] == 'System' ||
$tz == '' ) {
556 # Global offset in minutes.
557 if( isset($wgLocalTZoffset) ) $minDiff = $wgLocalTZoffset;
558 } else if ( $data[0] == 'Offset' ) {
559 $minDiff = intval( $data[1] );
561 $data = explode( ':', $tz );
562 if( count( $data ) == 2 ) {
563 $data[0] = intval( $data[0] );
564 $data[1] = intval( $data[1] );
565 $minDiff = abs( $data[0] ) * 60 +
$data[1];
566 if ( $data[0] < 0 ) $minDiff = -$minDiff;
568 $minDiff = intval( $data[0] ) * 60;
572 # No difference ? Return time unchanged
573 if ( 0 == $minDiff ) return $ts;
575 wfSuppressWarnings(); // E_STRICT system time bitching
576 # Generate an adjusted date; take advantage of the fact that mktime
577 # will normalize out-of-range values so we don't have to split $minDiff
578 # into hours and minutes.
580 (int)substr( $ts, 8, 2) ), # Hours
581 (int)substr( $ts, 10, 2 ) +
$minDiff, # Minutes
582 (int)substr( $ts, 12, 2 ), # Seconds
583 (int)substr( $ts, 4, 2 ), # Month
584 (int)substr( $ts, 6, 2 ), # Day
585 (int)substr( $ts, 0, 4 ) ); #Year
587 $date = date( 'YmdHis', $t );
594 * This is a workalike of PHP's date() function, but with better
595 * internationalisation, a reduced set of format characters, and a better
598 * Supported format characters are dDjlNwzWFmMntLoYyaAgGhHiscrU. See the
599 * PHP manual for definitions. "o" format character is supported since
600 * PHP 5.1.0, previous versions return literal o.
601 * There are a number of extensions, which start with "x":
603 * xn Do not translate digits of the next numeric format character
604 * xN Toggle raw digit (xn) flag, stays set until explicitly unset
605 * xr Use roman numerals for the next numeric format character
606 * xh Use hebrew numerals for the next numeric format character
608 * xg Genitive month name
610 * xij j (day number) in Iranian calendar
611 * xiF F (month name) in Iranian calendar
612 * xin n (month number) in Iranian calendar
613 * xiY Y (full year) in Iranian calendar
615 * xjj j (day number) in Hebrew calendar
616 * xjF F (month name) in Hebrew calendar
617 * xjt t (days in month) in Hebrew calendar
618 * xjx xg (genitive month name) in Hebrew calendar
619 * xjn n (month number) in Hebrew calendar
620 * xjY Y (full year) in Hebrew calendar
622 * xmj j (day number) in Hijri calendar
623 * xmF F (month name) in Hijri calendar
624 * xmn n (month number) in Hijri calendar
625 * xmY Y (full year) in Hijri calendar
627 * xkY Y (full year) in Thai solar calendar. Months and days are
628 * identical to the Gregorian calendar
629 * xoY Y (full year) in Minguo calendar or Juche year.
630 * Months and days are identical to the
632 * xtY Y (full year) in Japanese nengo. Months and days are
633 * identical to the Gregorian calendar
635 * Characters enclosed in double quotes will be considered literal (with
636 * the quotes themselves removed). Unmatched quotes will be considered
637 * literal quotes. Example:
639 * "The month is" F => The month is January
642 * Backslash escaping is also supported.
644 * Input timestamp is assumed to be pre-normalized to the desired local
647 * @param $format String
648 * @param $ts String: 14-character timestamp
651 * @todo emulation of "o" format character for PHP pre 5.1.0
652 * @todo handling of "o" format character for Iranian, Hebrew, Hijri & Thai?
654 function sprintfDate( $format, $ts ) {
667 for ( $p = 0; $p < strlen( $format ); $p++
) {
670 if ( $code == 'x' && $p < strlen( $format ) - 1 ) {
671 $code .= $format[++
$p];
674 if ( ( $code === 'xi' ||
$code == 'xj' ||
$code == 'xk' ||
$code == 'xm' ||
$code == 'xo' ||
$code == 'xt' ) && $p < strlen( $format ) - 1 ) {
675 $code .= $format[++
$p];
686 $rawToggle = !$rawToggle;
695 $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
698 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
699 $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
702 $num = substr( $ts, 6, 2 );
705 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
706 $s .= $this->getWeekdayAbbreviation( gmdate( 'w', $unix ) +
1 );
709 $num = intval( substr( $ts, 6, 2 ) );
712 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
716 if ( !$hijri ) $hijri = self
::tsToHijri( $ts );
720 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
724 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
725 $s .= $this->getWeekdayName( gmdate( 'w', $unix ) +
1 );
728 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
729 $w = gmdate( 'w', $unix );
733 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
734 $num = gmdate( 'w', $unix );
737 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
738 $num = gmdate( 'z', $unix );
741 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
742 $num = gmdate( 'W', $unix );
745 $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
748 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
749 $s .= $this->getIranianCalendarMonthName( $iranian[1] );
752 if ( !$hijri ) $hijri = self
::tsToHijri( $ts );
753 $s .= $this->getHijriCalendarMonthName( $hijri[1] );
756 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
757 $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
760 $num = substr( $ts, 4, 2 );
763 $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
766 $num = intval( substr( $ts, 4, 2 ) );
769 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
773 if ( !$hijri ) $hijri = self
::tsToHijri ( $ts );
777 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
781 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
782 $num = gmdate( 't', $unix );
785 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
789 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
790 $num = gmdate( 'L', $unix );
792 # 'o' is supported since PHP 5.1.0
793 # return literal if not supported
794 # TODO: emulation for pre 5.1.0 versions
796 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
797 if ( version_compare(PHP_VERSION
, '5.1.0') === 1 )
798 $num = date( 'o', $unix );
803 $num = substr( $ts, 0, 4 );
806 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
810 if ( !$hijri ) $hijri = self
::tsToHijri( $ts );
814 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
818 if ( !$thai ) $thai = self
::tsToYear( $ts, 'thai' );
822 if ( !$minguo ) $minguo = self
::tsToYear( $ts, 'minguo' );
826 if ( !$tenno ) $tenno = self
::tsToYear( $ts, 'tenno' );
830 $num = substr( $ts, 2, 2 );
833 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ?
'am' : 'pm';
836 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ?
'AM' : 'PM';
839 $h = substr( $ts, 8, 2 );
840 $num = $h %
12 ?
$h %
12 : 12;
843 $num = intval( substr( $ts, 8, 2 ) );
846 $h = substr( $ts, 8, 2 );
847 $num = sprintf( '%02d', $h %
12 ?
$h %
12 : 12 );
850 $num = substr( $ts, 8, 2 );
853 $num = substr( $ts, 10, 2 );
856 $num = substr( $ts, 12, 2 );
859 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
860 $s .= gmdate( 'c', $unix );
863 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
864 $s .= gmdate( 'r', $unix );
867 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
872 if ( $p < strlen( $format ) - 1 ) {
880 if ( $p < strlen( $format ) - 1 ) {
881 $endQuote = strpos( $format, '"', $p +
1 );
882 if ( $endQuote === false ) {
883 # No terminating quote, assume literal "
886 $s .= substr( $format, $p +
1, $endQuote - $p - 1 );
890 # Quote at end of string, assume literal "
897 if ( $num !== false ) {
898 if ( $rawToggle ||
$raw ) {
901 } elseif ( $roman ) {
902 $s .= self
::romanNumeral( $num );
904 } elseif( $hebrewNum ) {
905 $s .= self
::hebrewNumeral( $num );
908 $s .= $this->formatNum( $num, true );
916 private static $GREG_DAYS = array( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
917 private static $IRANIAN_DAYS = array( 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 );
919 * Algorithm by Roozbeh Pournader and Mohammad Toossi to convert
920 * Gregorian dates to Iranian dates. Originally written in C, it
921 * is released under the terms of GNU Lesser General Public
922 * License. Conversion to PHP was performed by Niklas Laxström.
924 * Link: http://www.farsiweb.info/jalali/jalali.c
926 private static function tsToIranian( $ts ) {
927 $gy = substr( $ts, 0, 4 ) -1600;
928 $gm = substr( $ts, 4, 2 ) -1;
929 $gd = substr( $ts, 6, 2 ) -1;
931 # Days passed from the beginning (including leap years)
934 - floor(($gy+
99) / 100)
935 +
floor(($gy+
399) / 400);
938 // Add days of the past months of this year
939 for( $i = 0; $i < $gm; $i++
) {
940 $gDayNo +
= self
::$GREG_DAYS[$i];
944 if ( $gm > 1 && (($gy%4
===0 && $gy%100
!==0 ||
($gy%400
==0)))) {
948 // Days passed in current month
951 $jDayNo = $gDayNo - 79;
953 $jNp = floor($jDayNo / 12053);
956 $jy = 979 +
33*$jNp +
4*floor($jDayNo/1461);
959 if ( $jDayNo >= 366 ) {
960 $jy +
= floor(($jDayNo-1)/365);
961 $jDayNo = floor(($jDayNo-1)%365
);
964 for ( $i = 0; $i < 11 && $jDayNo >= self
::$IRANIAN_DAYS[$i]; $i++
) {
965 $jDayNo -= self
::$IRANIAN_DAYS[$i];
971 return array($jy, $jm, $jd);
974 * Converting Gregorian dates to Hijri dates.
976 * Based on a PHP-Nuke block by Sharjeel which is released under GNU/GPL license
978 * @link http://phpnuke.org/modules.php?name=News&file=article&sid=8234&mode=thread&order=0&thold=0
980 private static function tsToHijri ( $ts ) {
981 $year = substr( $ts, 0, 4 );
982 $month = substr( $ts, 4, 2 );
983 $day = substr( $ts, 6, 2 );
992 if (($zy>1582)||
(($zy==1582)&&($zm>10))||
(($zy==1582)&&($zm==10)&&($zd>14)))
996 $zjd=(int)((1461*($zy +
4800 +
(int)( ($zm-14) /12) ))/4) +
(int)((367*($zm-2-12*((int)(($zm-14)/12))))/12)-(int)((3*(int)(( ($zy+
4900+
(int)(($zm-14)/12))/100)))/4)+
$zd-32075;
1000 $zjd = 367*$zy-(int)((7*($zy+
5001+
(int)(($zm-9)/7)))/4)+
(int)((275*$zm)/9)+
$zd+
1729777;
1003 $zl=$zjd-1948440+
10632;
1004 $zn=(int)(($zl-1)/10631);
1005 $zl=$zl-10631*$zn+
354;
1006 $zj=((int)((10985-$zl)/5316))*((int)((50*$zl)/17719))+
((int)($zl/5670))*((int)((43*$zl)/15238));
1007 $zl=$zl-((int)((30-$zj)/15))*((int)((17719*$zj)/50))-((int)($zj/16))*((int)((15238*$zj)/43))+
29;
1008 $zm=(int)((24*$zl)/709);
1009 $zd=$zl-(int)((709*$zm)/24);
1012 return array ($zy, $zm, $zd);
1016 * Converting Gregorian dates to Hebrew dates.
1018 * Based on a JavaScript code by Abu Mami and Yisrael Hersch
1019 * (abu-mami@kaluach.net, http://www.kaluach.net), who permitted
1020 * to translate the relevant functions into PHP and release them under
1023 * The months are counted from Tishrei = 1. In a leap year, Adar I is 13
1024 * and Adar II is 14. In a non-leap year, Adar is 6.
1026 private static function tsToHebrew( $ts ) {
1028 $year = substr( $ts, 0, 4 );
1029 $month = substr( $ts, 4, 2 );
1030 $day = substr( $ts, 6, 2 );
1032 # Calculate Hebrew year
1033 $hebrewYear = $year +
3760;
1035 # Month number when September = 1, August = 12
1044 # Calculate day of year from 1 September
1046 for( $i = 1; $i < $month; $i++
) {
1050 # Check if the year is leap
1051 if( $year %
400 == 0 ||
( $year %
4 == 0 && $year %
100 > 0 ) ) {
1054 } elseif( $i == 8 ||
$i == 10 ||
$i == 1 ||
$i == 3 ) {
1061 # Calculate the start of the Hebrew year
1062 $start = self
::hebrewYearStart( $hebrewYear );
1064 # Calculate next year's start
1065 if( $dayOfYear <= $start ) {
1066 # Day is before the start of the year - it is the previous year
1068 $nextStart = $start;
1072 # Add days since previous year's 1 September
1074 if( ( $year %
400 == 0 ) ||
( $year %
100 != 0 && $year %
4 == 0 ) ) {
1078 # Start of the new (previous) year
1079 $start = self
::hebrewYearStart( $hebrewYear );
1082 $nextStart = self
::hebrewYearStart( $hebrewYear +
1 );
1085 # Calculate Hebrew day of year
1086 $hebrewDayOfYear = $dayOfYear - $start;
1088 # Difference between year's days
1089 $diff = $nextStart - $start;
1090 # Add 12 (or 13 for leap years) days to ignore the difference between
1091 # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
1092 # difference is only about the year type
1093 if( ( $year %
400 == 0 ) ||
( $year %
100 != 0 && $year %
4 == 0 ) ) {
1099 # Check the year pattern, and is leap year
1100 # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
1101 # This is mod 30, to work on both leap years (which add 30 days of Adar I)
1102 # and non-leap years
1103 $yearPattern = $diff %
30;
1104 # Check if leap year
1105 $isLeap = $diff >= 30;
1107 # Calculate day in the month from number of day in the Hebrew year
1108 # Don't check Adar - if the day is not in Adar, we will stop before;
1109 # if it is in Adar, we will use it to check if it is Adar I or Adar II
1110 $hebrewDay = $hebrewDayOfYear;
1113 while( $hebrewMonth <= 12 ) {
1114 # Calculate days in this month
1115 if( $isLeap && $hebrewMonth == 6 ) {
1116 # Adar in a leap year
1118 # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
1120 if( $hebrewDay <= $days ) {
1124 # Subtract the days of Adar I
1125 $hebrewDay -= $days;
1128 if( $hebrewDay <= $days ) {
1134 } elseif( $hebrewMonth == 2 && $yearPattern == 2 ) {
1135 # Cheshvan in a complete year (otherwise as the rule below)
1137 } elseif( $hebrewMonth == 3 && $yearPattern == 0 ) {
1138 # Kislev in an incomplete year (otherwise as the rule below)
1141 # Odd months have 30 days, even have 29
1142 $days = 30 - ( $hebrewMonth - 1 ) %
2;
1144 if( $hebrewDay <= $days ) {
1145 # In the current month
1148 # Subtract the days of the current month
1149 $hebrewDay -= $days;
1150 # Try in the next month
1155 return array( $hebrewYear, $hebrewMonth, $hebrewDay, $days );
1159 * This calculates the Hebrew year start, as days since 1 September.
1160 * Based on Carl Friedrich Gauss algorithm for finding Easter date.
1161 * Used for Hebrew date.
1163 private static function hebrewYearStart( $year ) {
1164 $a = intval( ( 12 * ( $year - 1 ) +
17 ) %
19 );
1165 $b = intval( ( $year - 1 ) %
4 );
1166 $m = 32.044093161144 +
1.5542417966212 * $a +
$b / 4.0 - 0.0031777940220923 * ( $year - 1 );
1170 $Mar = intval( $m );
1176 $c = intval( ( $Mar +
3 * ( $year - 1 ) +
5 * $b +
5 ) %
7);
1177 if( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
1179 } else if( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
1181 } else if( $c == 2 ||
$c == 4 ||
$c == 6 ) {
1185 $Mar +
= intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
1190 * Algorithm to convert Gregorian dates to Thai solar dates,
1191 * Minguo dates or Minguo dates.
1193 * Link: http://en.wikipedia.org/wiki/Thai_solar_calendar
1194 * http://en.wikipedia.org/wiki/Minguo_calendar
1195 * http://en.wikipedia.org/wiki/Japanese_era_name
1197 * @param $ts String: 14-character timestamp, calender name
1198 * @return array converted year, month, day
1200 private static function tsToYear( $ts, $cName ) {
1201 $gy = substr( $ts, 0, 4 );
1202 $gm = substr( $ts, 4, 2 );
1203 $gd = substr( $ts, 6, 2 );
1205 if (!strcmp($cName,'thai')) {
1207 # Add 543 years to the Gregorian calendar
1208 # Months and days are identical
1209 $gy_offset = $gy +
543;
1210 } else if ((!strcmp($cName,'minguo')) ||
!strcmp($cName,'juche')) {
1212 # Deduct 1911 years from the Gregorian calendar
1213 # Months and days are identical
1214 $gy_offset = $gy - 1911;
1215 } else if (!strcmp($cName,'tenno')) {
1216 # Nengō dates up to Meiji period
1217 # Deduct years from the Gregorian calendar
1218 # depending on the nengo periods
1219 # Months and days are identical
1220 if (($gy < 1912) ||
(($gy == 1912) && ($gm < 7)) ||
(($gy == 1912) && ($gm == 7) && ($gd < 31))) {
1222 $gy_gannen = $gy - 1868 +
1;
1223 $gy_offset = $gy_gannen;
1224 if ($gy_gannen == 1)
1226 $gy_offset = '明治'.$gy_offset;
1227 } else if ((($gy == 1912) && ($gm == 7) && ($gd == 31)) ||
(($gy == 1912) && ($gm >= 8)) ||
(($gy > 1912) && ($gy < 1926)) ||
(($gy == 1926) && ($gm < 12)) ||
(($gy == 1926) && ($gm == 12) && ($gd < 26))) {
1229 $gy_gannen = $gy - 1912 +
1;
1230 $gy_offset = $gy_gannen;
1231 if ($gy_gannen == 1)
1233 $gy_offset = '大正'.$gy_offset;
1234 } else if ((($gy == 1926) && ($gm == 12) && ($gd >= 26)) ||
(($gy > 1926) && ($gy < 1989)) ||
(($gy == 1989) && ($gm == 1) && ($gd < 8))) {
1236 $gy_gannen = $gy - 1926 +
1;
1237 $gy_offset = $gy_gannen;
1238 if ($gy_gannen == 1)
1240 $gy_offset = '昭和'.$gy_offset;
1243 $gy_gannen = $gy - 1989 +
1;
1244 $gy_offset = $gy_gannen;
1245 if ($gy_gannen == 1)
1247 $gy_offset = '平成'.$gy_offset;
1253 return array( $gy_offset, $gm, $gd );
1257 * Roman number formatting up to 3000
1259 static function romanNumeral( $num ) {
1260 static $table = array(
1261 array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ),
1262 array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ),
1263 array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ),
1264 array( '', 'M', 'MM', 'MMM' )
1267 $num = intval( $num );
1268 if ( $num > 3000 ||
$num <= 0 ) {
1273 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1274 if ( $num >= $pow10 ) {
1275 $s .= $table[$i][floor($num / $pow10)];
1277 $num = $num %
$pow10;
1283 * Hebrew Gematria number formatting up to 9999
1285 static function hebrewNumeral( $num ) {
1286 static $table = array(
1287 array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ),
1288 array( '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ),
1289 array( '', 'ק', 'ר', 'ש', 'ת', 'תק', 'תר', 'תש', 'תת', 'תתק', 'תתר' ),
1290 array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' )
1293 $num = intval( $num );
1294 if ( $num > 9999 ||
$num <= 0 ) {
1299 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1300 if ( $num >= $pow10 ) {
1301 if ( $num == 15 ||
$num == 16 ) {
1302 $s .= $table[0][9] . $table[0][$num - 9];
1305 $s .= $table[$i][intval( ( $num / $pow10 ) )];
1306 if( $pow10 == 1000 ) {
1311 $num = $num %
$pow10;
1313 if( strlen( $s ) == 2 ) {
1316 $str = substr( $s, 0, strlen( $s ) - 2 ) . '"';
1317 $str .= substr( $s, strlen( $s ) - 2, 2 );
1319 $start = substr( $str, 0, strlen( $str ) - 2 );
1320 $end = substr( $str, strlen( $str ) - 2 );
1323 $str = $start . 'ך';
1326 $str = $start . 'ם';
1329 $str = $start . 'ן';
1332 $str = $start . 'ף';
1335 $str = $start . 'ץ';
1342 * This is meant to be used by time(), date(), and timeanddate() to get
1343 * the date preference they're supposed to use, it should be used in
1347 * function timeanddate([...], $format = true) {
1348 * $datePreference = $this->dateFormat($format);
1353 * @param $usePrefs Mixed: if true, the user's preference is used
1354 * if false, the site/language default is used
1355 * if int/string, assumed to be a format.
1358 function dateFormat( $usePrefs = true ) {
1361 if( is_bool( $usePrefs ) ) {
1363 $datePreference = $wgUser->getDatePreference();
1365 $options = User
::getDefaultOptions();
1366 $datePreference = (string)$options['date'];
1369 $datePreference = (string)$usePrefs;
1373 if( $datePreference == '' ) {
1377 return $datePreference;
1381 * Get a format string for a given type and preference
1382 * @param $type May be date, time or both
1383 * @param $pref The format name as it appears in Messages*.php
1385 function getDateFormatString( $type, $pref ) {
1386 if ( !isset( $this->dateFormatStrings
[$type][$pref] ) ) {
1387 if ( $pref == 'default' ) {
1388 $pref = $this->getDefaultDateFormat();
1389 $df = self
::$dataCache->getSubitem( $this->mCode
, 'dateFormats', "$pref $type" );
1391 $df = self
::$dataCache->getSubitem( $this->mCode
, 'dateFormats', "$pref $type" );
1392 if ( is_null( $df ) ) {
1393 $pref = $this->getDefaultDateFormat();
1394 $df = self
::$dataCache->getSubitem( $this->mCode
, 'dateFormats', "$pref $type" );
1397 $this->dateFormatStrings
[$type][$pref] = $df;
1399 return $this->dateFormatStrings
[$type][$pref];
1403 * @param $ts Mixed: the time format which needs to be turned into a
1404 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1405 * @param $adj Bool: whether to adjust the time output according to the
1406 * user configured offset ($timecorrection)
1407 * @param $format Mixed: true to use user's date format preference
1408 * @param $timecorrection String: the time offset as returned by
1409 * validateTimeZone() in Special:Preferences
1412 function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
1414 $ts = $this->userAdjust( $ts, $timecorrection );
1416 $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) );
1417 return $this->sprintfDate( $df, $ts );
1421 * @param $ts Mixed: the time format which needs to be turned into a
1422 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1423 * @param $adj Bool: whether to adjust the time output according to the
1424 * user configured offset ($timecorrection)
1425 * @param $format Mixed: true to use user's date format preference
1426 * @param $timecorrection String: the time offset as returned by
1427 * validateTimeZone() in Special:Preferences
1430 function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
1432 $ts = $this->userAdjust( $ts, $timecorrection );
1434 $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) );
1435 return $this->sprintfDate( $df, $ts );
1439 * @param $ts Mixed: the time format which needs to be turned into a
1440 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1441 * @param $adj Bool: whether to adjust the time output according to the
1442 * user configured offset ($timecorrection)
1443 * @param $format Mixed: what format to return, if it's false output the
1444 * default one (default true)
1445 * @param $timecorrection String: the time offset as returned by
1446 * validateTimeZone() in Special:Preferences
1449 function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false) {
1450 $ts = wfTimestamp( TS_MW
, $ts );
1452 $ts = $this->userAdjust( $ts, $timecorrection );
1454 $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) );
1455 return $this->sprintfDate( $df, $ts );
1458 function getMessage( $key ) {
1459 return self
::$dataCache->getSubitem( $this->mCode
, 'messages', $key );
1462 function getAllMessages() {
1463 return self
::$dataCache->getItem( $this->mCode
, 'messages' );
1466 function iconv( $in, $out, $string ) {
1467 # For most languages, this is a wrapper for iconv
1468 return iconv( $in, $out . '//IGNORE', $string );
1471 // callback functions for uc(), lc(), ucwords(), ucwordbreaks()
1472 function ucwordbreaksCallbackAscii($matches){
1473 return $this->ucfirst($matches[1]);
1476 function ucwordbreaksCallbackMB($matches){
1477 return mb_strtoupper($matches[0]);
1480 function ucCallback($matches){
1481 list( $wikiUpperChars ) = self
::getCaseMaps();
1482 return strtr( $matches[1], $wikiUpperChars );
1485 function lcCallback($matches){
1486 list( , $wikiLowerChars ) = self
::getCaseMaps();
1487 return strtr( $matches[1], $wikiLowerChars );
1490 function ucwordsCallbackMB($matches){
1491 return mb_strtoupper($matches[0]);
1494 function ucwordsCallbackWiki($matches){
1495 list( $wikiUpperChars ) = self
::getCaseMaps();
1496 return strtr( $matches[0], $wikiUpperChars );
1499 function ucfirst( $str ) {
1500 if ( empty($str) ) return $str;
1501 if ( ord($str[0]) < 128 ) return ucfirst($str);
1502 else return self
::uc($str,true); // fall back to more complex logic in case of multibyte strings
1505 function uc( $str, $first = false ) {
1506 if ( function_exists( 'mb_strtoupper' ) ) {
1508 if ( self
::isMultibyte( $str ) ) {
1509 return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
1511 return ucfirst( $str );
1514 return self
::isMultibyte( $str ) ?
mb_strtoupper( $str ) : strtoupper( $str );
1517 if ( self
::isMultibyte( $str ) ) {
1518 list( $wikiUpperChars ) = $this->getCaseMaps();
1519 $x = $first ?
'^' : '';
1520 return preg_replace_callback(
1521 "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
1522 array($this,"ucCallback"),
1526 return $first ?
ucfirst( $str ) : strtoupper( $str );
1531 function lcfirst( $str ) {
1532 if ( empty($str) ) return $str;
1533 if ( is_string( $str ) && ord($str[0]) < 128 ) {
1534 // editing string in place = cool
1535 $str[0]=strtolower($str[0]);
1538 else return self
::lc( $str, true );
1541 function lc( $str, $first = false ) {
1542 if ( function_exists( 'mb_strtolower' ) )
1544 if ( self
::isMultibyte( $str ) )
1545 return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
1547 return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
1549 return self
::isMultibyte( $str ) ?
mb_strtolower( $str ) : strtolower( $str );
1551 if ( self
::isMultibyte( $str ) ) {
1552 list( , $wikiLowerChars ) = self
::getCaseMaps();
1553 $x = $first ?
'^' : '';
1554 return preg_replace_callback(
1555 "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
1556 array($this,"lcCallback"),
1560 return $first ?
strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str );
1563 function isMultibyte( $str ) {
1564 return (bool)preg_match( '/[\x80-\xff]/', $str );
1567 function ucwords($str) {
1568 if ( self
::isMultibyte( $str ) ) {
1569 $str = self
::lc($str);
1571 // regexp to find first letter in each word (i.e. after each space)
1572 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
1574 // function to use to capitalize a single char
1575 if ( function_exists( 'mb_strtoupper' ) )
1576 return preg_replace_callback(
1578 array($this,"ucwordsCallbackMB"),
1582 return preg_replace_callback(
1584 array($this,"ucwordsCallbackWiki"),
1589 return ucwords( strtolower( $str ) );
1592 # capitalize words at word breaks
1593 function ucwordbreaks($str){
1594 if (self
::isMultibyte( $str ) ) {
1595 $str = self
::lc($str);
1597 // since \b doesn't work for UTF-8, we explicitely define word break chars
1598 $breaks= "[ \-\(\)\}\{\.,\?!]";
1600 // find first letter after word break
1601 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
1603 if ( function_exists( 'mb_strtoupper' ) )
1604 return preg_replace_callback(
1606 array($this,"ucwordbreaksCallbackMB"),
1610 return preg_replace_callback(
1612 array($this,"ucwordsCallbackWiki"),
1617 return preg_replace_callback(
1618 '/\b([\w\x80-\xff]+)\b/',
1619 array($this,"ucwordbreaksCallbackAscii"),
1624 * Return a case-folded representation of $s
1626 * This is a representation such that caseFold($s1)==caseFold($s2) if $s1
1627 * and $s2 are the same except for the case of their characters. It is not
1628 * necessary for the value returned to make sense when displayed.
1630 * Do *not* perform any other normalisation in this function. If a caller
1631 * uses this function when it should be using a more general normalisation
1632 * function, then fix the caller.
1634 function caseFold( $s ) {
1635 return $this->uc( $s );
1638 function checkTitleEncoding( $s ) {
1639 if( is_array( $s ) ) {
1640 wfDebugDieBacktrace( 'Given array to checkTitleEncoding.' );
1642 # Check for non-UTF-8 URLs
1643 $ishigh = preg_match( '/[\x80-\xff]/', $s);
1644 if(!$ishigh) return $s;
1646 $isutf8 = preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
1647 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s );
1648 if( $isutf8 ) return $s;
1650 return $this->iconv( $this->fallback8bitEncoding(), "utf-8", $s );
1653 function fallback8bitEncoding() {
1654 return self
::$dataCache->getItem( $this->mCode
, 'fallback8bitEncoding' );
1658 * Most writing systems use whitespace to break up words.
1659 * Some languages such as Chinese don't conventionally do this,
1660 * which requires special handling when breaking up words for
1663 function hasWordBreaks() {
1668 * Some languages have special punctuation to strip out
1669 * or characters which need to be converted for MySQL's
1670 * indexing to grok it correctly. Make such changes here.
1672 * @param $string String
1675 function stripForSearch( $string ) {
1677 if ( $wgDBtype != 'mysql' ) {
1682 wfProfileIn( __METHOD__
);
1684 // MySQL fulltext index doesn't grok utf-8, so we
1685 // need to fold cases and convert to hex
1686 $out = preg_replace_callback(
1687 "/([\\xc0-\\xff][\\x80-\\xbf]*)/",
1688 array( $this, 'stripForSearchCallback' ),
1689 $this->lc( $string ) );
1691 // And to add insult to injury, the default indexing
1692 // ignores short words... Pad them so we can pass them
1693 // through without reconfiguring the server...
1694 $minLength = $this->minSearchLength();
1695 if( $minLength > 1 ) {
1697 $out = preg_replace(
1703 // Periods within things like hostnames and IP addresses
1704 // are also important -- we want a search for "example.com"
1705 // or "192.168.1.1" to work sanely.
1707 // MySQL's search seems to ignore them, so you'd match on
1708 // "example.wikipedia.com" and "192.168.83.1" as well.
1709 $out = preg_replace(
1714 wfProfileOut( __METHOD__
);
1719 * Armor a case-folded UTF-8 string to get through MySQL's
1720 * fulltext search without being mucked up by funny charset
1721 * settings or anything else of the sort.
1723 protected function stripForSearchCallback( $matches ) {
1724 return 'u8' . bin2hex( $matches[1] );
1728 * Check MySQL server's ft_min_word_len setting so we know
1729 * if we need to pad short words...
1731 protected function minSearchLength() {
1732 if( is_null( $this->minSearchLength
) ) {
1733 $sql = "show global variables like 'ft\\_min\\_word\\_len'";
1734 $dbr = wfGetDB( DB_SLAVE
);
1735 $result = $dbr->query( $sql );
1736 $row = $result->fetchObject();
1739 if( $row && $row->Variable_name
== 'ft_min_word_len' ) {
1740 $this->minSearchLength
= intval( $row->Value
);
1742 $this->minSearchLength
= 0;
1745 return $this->minSearchLength
;
1748 function convertForSearchResult( $termsArray ) {
1749 # some languages, e.g. Chinese, need to do a conversion
1750 # in order for search results to be displayed correctly
1755 * Get the first character of a string.
1760 function firstChar( $s ) {
1762 preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
1763 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/', $s, $matches);
1765 if ( isset( $matches[1] ) ) {
1766 if ( strlen( $matches[1] ) != 3 ) {
1770 // Break down Hangul syllables to grab the first jamo
1771 $code = utf8ToCodepoint( $matches[1] );
1772 if ( $code < 0xac00 ||
0xd7a4 <= $code) {
1774 } elseif ( $code < 0xb098 ) {
1775 return "\xe3\x84\xb1";
1776 } elseif ( $code < 0xb2e4 ) {
1777 return "\xe3\x84\xb4";
1778 } elseif ( $code < 0xb77c ) {
1779 return "\xe3\x84\xb7";
1780 } elseif ( $code < 0xb9c8 ) {
1781 return "\xe3\x84\xb9";
1782 } elseif ( $code < 0xbc14 ) {
1783 return "\xe3\x85\x81";
1784 } elseif ( $code < 0xc0ac ) {
1785 return "\xe3\x85\x82";
1786 } elseif ( $code < 0xc544 ) {
1787 return "\xe3\x85\x85";
1788 } elseif ( $code < 0xc790 ) {
1789 return "\xe3\x85\x87";
1790 } elseif ( $code < 0xcc28 ) {
1791 return "\xe3\x85\x88";
1792 } elseif ( $code < 0xce74 ) {
1793 return "\xe3\x85\x8a";
1794 } elseif ( $code < 0xd0c0 ) {
1795 return "\xe3\x85\x8b";
1796 } elseif ( $code < 0xd30c ) {
1797 return "\xe3\x85\x8c";
1798 } elseif ( $code < 0xd558 ) {
1799 return "\xe3\x85\x8d";
1801 return "\xe3\x85\x8e";
1808 function initEncoding() {
1809 # Some languages may have an alternate char encoding option
1810 # (Esperanto X-coding, Japanese furigana conversion, etc)
1811 # If this language is used as the primary content language,
1812 # an override to the defaults can be set here on startup.
1815 function recodeForEdit( $s ) {
1816 # For some languages we'll want to explicitly specify
1817 # which characters make it into the edit box raw
1818 # or are converted in some way or another.
1819 # Note that if wgOutputEncoding is different from
1820 # wgInputEncoding, this text will be further converted
1821 # to wgOutputEncoding.
1822 global $wgEditEncoding;
1823 if( $wgEditEncoding == '' or
1824 $wgEditEncoding == 'UTF-8' ) {
1827 return $this->iconv( 'UTF-8', $wgEditEncoding, $s );
1831 function recodeInput( $s ) {
1832 # Take the previous into account.
1833 global $wgEditEncoding;
1834 if($wgEditEncoding != "") {
1835 $enc = $wgEditEncoding;
1839 if( $enc == 'UTF-8' ) {
1842 return $this->iconv( $enc, 'UTF-8', $s );
1847 * For right-to-left language support
1852 return self
::$dataCache->getItem( $this->mCode
, 'rtl' );
1856 * A hidden direction mark (LRM or RLM), depending on the language direction
1860 function getDirMark() {
1861 return $this->isRTL() ?
"\xE2\x80\x8F" : "\xE2\x80\x8E";
1864 function capitalizeAllNouns() {
1865 return self
::$dataCache->getItem( $this->mCode
, 'capitalizeAllNouns' );
1869 * An arrow, depending on the language direction
1873 function getArrow() {
1874 return $this->isRTL() ?
'←' : '→';
1878 * To allow "foo[[bar]]" to extend the link over the whole word "foobar"
1882 function linkPrefixExtension() {
1883 return self
::$dataCache->getItem( $this->mCode
, 'linkPrefixExtension' );
1886 function getMagicWords() {
1887 return self
::$dataCache->getItem( $this->mCode
, 'magicWords' );
1890 # Fill a MagicWord object with data from here
1891 function getMagic( &$mw ) {
1892 if ( !$this->mMagicHookDone
) {
1893 $this->mMagicHookDone
= true;
1894 wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions
, $this->getCode() ) );
1896 if ( isset( $this->mMagicExtensions
[$mw->mId
] ) ) {
1897 $rawEntry = $this->mMagicExtensions
[$mw->mId
];
1899 $magicWords = $this->getMagicWords();
1900 if ( isset( $magicWords[$mw->mId
] ) ) {
1901 $rawEntry = $magicWords[$mw->mId
];
1907 if( !is_array( $rawEntry ) ) {
1908 error_log( "\"$rawEntry\" is not a valid magic thingie for \"$mw->mId\"" );
1910 $mw->mCaseSensitive
= $rawEntry[0];
1911 $mw->mSynonyms
= array_slice( $rawEntry, 1 );
1916 * Add magic words to the extension array
1918 function addMagicWordsByLang( $newWords ) {
1919 $code = $this->getCode();
1920 $fallbackChain = array();
1921 while ( $code && !in_array( $code, $fallbackChain ) ) {
1922 $fallbackChain[] = $code;
1923 $code = self
::getFallbackFor( $code );
1925 if ( !in_array( 'en', $fallbackChain ) ) {
1926 $fallbackChain[] = 'en';
1928 $fallbackChain = array_reverse( $fallbackChain );
1929 foreach ( $fallbackChain as $code ) {
1930 if ( isset( $newWords[$code] ) ) {
1931 $this->mMagicExtensions
= $newWords[$code] +
$this->mMagicExtensions
;
1937 * Get special page names, as an associative array
1938 * case folded alias => real name
1940 function getSpecialPageAliases() {
1941 // Cache aliases because it may be slow to load them
1942 if ( is_null( $this->mExtendedSpecialPageAliases
) ) {
1944 $this->mExtendedSpecialPageAliases
=
1945 self
::$dataCache->getItem( $this->mCode
, 'specialPageAliases' );
1946 wfRunHooks( 'LanguageGetSpecialPageAliases',
1947 array( &$this->mExtendedSpecialPageAliases
, $this->getCode() ) );
1950 return $this->mExtendedSpecialPageAliases
;
1954 * Italic is unsuitable for some languages
1956 * @param $text String: the text to be emphasized.
1959 function emphasize( $text ) {
1960 return "<em>$text</em>";
1964 * Normally we output all numbers in plain en_US style, that is
1965 * 293,291.235 for twohundredninetythreethousand-twohundredninetyone
1966 * point twohundredthirtyfive. However this is not sutable for all
1967 * languages, some such as Pakaran want ੨੯੩,੨੯੫.੨੩੫ and others such as
1968 * Icelandic just want to use commas instead of dots, and dots instead
1969 * of commas like "293.291,235".
1971 * An example of this function being called:
1973 * wfMsg( 'message', $wgLang->formatNum( $num ) )
1976 * See LanguageGu.php for the Gujarati implementation and
1977 * $separatorTransformTable on MessageIs.php for
1978 * the , => . and . => , implementation.
1980 * @todo check if it's viable to use localeconv() for the decimal
1982 * @param $number Mixed: the string to be formatted, should be an integer
1983 * or a floating point number.
1984 * @param $nocommafy Bool: set to true for special numbers like dates
1987 function formatNum( $number, $nocommafy = false ) {
1988 global $wgTranslateNumerals;
1990 $number = $this->commafy($number);
1991 $s = $this->separatorTransformTable();
1992 if ($s) { $number = strtr($number, $s); }
1995 if ($wgTranslateNumerals) {
1996 $s = $this->digitTransformTable();
1997 if ($s) { $number = strtr($number, $s); }
2003 function parseFormattedNumber( $number ) {
2004 $s = $this->digitTransformTable();
2005 if ($s) { $number = strtr($number, array_flip($s)); }
2007 $s = $this->separatorTransformTable();
2008 if ($s) { $number = strtr($number, array_flip($s)); }
2010 $number = strtr( $number, array (',' => '') );
2015 * Adds commas to a given number
2020 function commafy($_) {
2021 return strrev((string)preg_replace('/(\d{3})(?=\d)(?!\d*\.)/','$1,',strrev($_)));
2024 function digitTransformTable() {
2025 return self
::$dataCache->getItem( $this->mCode
, 'digitTransformTable' );
2028 function separatorTransformTable() {
2029 return self
::$dataCache->getItem( $this->mCode
, 'separatorTransformTable' );
2034 * Take a list of strings and build a locale-friendly comma-separated
2035 * list, using the local comma-separator message.
2036 * The last two strings are chained with an "and".
2041 function listToText( $l ) {
2043 $m = count( $l ) - 1;
2045 return $l[0] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $l[1];
2048 for ( $i = $m; $i >= 0; $i-- ) {
2051 } else if( $i == $m - 1 ) {
2052 $s = $l[$i] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $s;
2054 $s = $l[$i] . $this->getMessageFromDB( 'comma-separator' ) . $s;
2062 * Take a list of strings and build a locale-friendly comma-separated
2063 * list, using the local comma-separator message.
2064 * @param $list array of strings to put in a comma list
2067 function commaList( $list ) {
2070 wfMsgExt( 'comma-separator', array( 'parsemag', 'escapenoentities', 'language' => $this ) ) );
2074 * Take a list of strings and build a locale-friendly semicolon-separated
2075 * list, using the local semicolon-separator message.
2076 * @param $list array of strings to put in a semicolon list
2079 function semicolonList( $list ) {
2082 wfMsgExt( 'semicolon-separator', array( 'parsemag', 'escapenoentities', 'language' => $this ) ) );
2086 * Same as commaList, but separate it with the pipe instead.
2087 * @param $list array of strings to put in a pipe list
2090 function pipeList( $list ) {
2093 wfMsgExt( 'pipe-separator', array( 'escapenoentities', 'language' => $this ) ) );
2097 * Truncate a string to a specified length in bytes, appending an optional
2098 * string (e.g. for ellipses)
2100 * The database offers limited byte lengths for some columns in the database;
2101 * multi-byte character sets mean we need to ensure that only whole characters
2102 * are included, otherwise broken characters can be passed to the user
2104 * If $length is negative, the string will be truncated from the beginning
2106 * @param $string String to truncate
2107 * @param $length Int: maximum length (excluding ellipses)
2108 * @param $ellipsis String to append to the truncated text
2111 function truncate( $string, $length, $ellipsis = '...' ) {
2112 # Use the localized ellipsis character
2113 if( $ellipsis == '...' ) {
2114 $ellipsis = wfMsgExt( 'ellipsis', array( 'escapenoentities', 'language' => $this ) );
2117 if( $length == 0 ) {
2120 if ( strlen( $string ) <= abs( $length ) ) {
2124 $string = substr( $string, 0, $length );
2125 $char = ord( $string[strlen( $string ) - 1] );
2127 if ($char >= 0xc0) {
2128 # We got the first byte only of a multibyte char; remove it.
2129 $string = substr( $string, 0, -1 );
2130 } elseif( $char >= 0x80 &&
2131 preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
2132 '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) ) {
2133 # We chopped in the middle of a character; remove it
2136 return $string . $ellipsis;
2138 $string = substr( $string, $length );
2139 $char = ord( $string[0] );
2140 if( $char >= 0x80 && $char < 0xc0 ) {
2141 # We chopped in the middle of a character; remove the whole thing
2142 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
2144 return $ellipsis . $string;
2149 * Grammatical transformations, needed for inflected languages
2150 * Invoked by putting {{grammar:case|word}} in a message
2152 * @param $word string
2153 * @param $case string
2156 function convertGrammar( $word, $case ) {
2157 global $wgGrammarForms;
2158 if ( isset($wgGrammarForms[$this->getCode()][$case][$word]) ) {
2159 return $wgGrammarForms[$this->getCode()][$case][$word];
2165 * Provides an alternative text depending on specified gender.
2166 * Usage {{gender:username|masculine|feminine|neutral}}.
2167 * username is optional, in which case the gender of current user is used,
2168 * but only in (some) interface messages; otherwise default gender is used.
2169 * If second or third parameter are not specified, masculine is used.
2170 * These details may be overriden per language.
2172 function gender( $gender, $forms ) {
2173 if ( !count($forms) ) { return ''; }
2174 $forms = $this->preConvertPlural( $forms, 2 );
2175 if ( $gender === 'male' ) return $forms[0];
2176 if ( $gender === 'female' ) return $forms[1];
2177 return isset($forms[2]) ?
$forms[2] : $forms[0];
2181 * Plural form transformations, needed for some languages.
2182 * For example, there are 3 form of plural in Russian and Polish,
2183 * depending on "count mod 10". See [[w:Plural]]
2184 * For English it is pretty simple.
2186 * Invoked by putting {{plural:count|wordform1|wordform2}}
2187 * or {{plural:count|wordform1|wordform2|wordform3}}
2189 * Example: {{plural:{{NUMBEROFARTICLES}}|article|articles}}
2191 * @param $count Integer: non-localized number
2192 * @param $forms Array: different plural forms
2193 * @return string Correct form of plural for $count in this language
2195 function convertPlural( $count, $forms ) {
2196 if ( !count($forms) ) { return ''; }
2197 $forms = $this->preConvertPlural( $forms, 2 );
2199 return ( $count == 1 ) ?
$forms[0] : $forms[1];
2203 * Checks that convertPlural was given an array and pads it to requested
2204 * amound of forms by copying the last one.
2206 * @param $count Integer: How many forms should there be at least
2207 * @param $forms Array of forms given to convertPlural
2208 * @return array Padded array of forms or an exception if not an array
2210 protected function preConvertPlural( /* Array */ $forms, $count ) {
2211 while ( count($forms) < $count ) {
2212 $forms[] = $forms[count($forms)-1];
2218 * For translaing of expiry times
2219 * @param $str String: the validated block time in English
2220 * @return Somehow translated block time
2221 * @see LanguageFi.php for example implementation
2223 function translateBlockExpiry( $str ) {
2225 $scBlockExpiryOptions = $this->getMessageFromDB( 'ipboptions' );
2227 if ( $scBlockExpiryOptions == '-') {
2231 foreach (explode(',', $scBlockExpiryOptions) as $option) {
2232 if ( strpos($option, ":") === false )
2234 list($show, $value) = explode(":", $option);
2235 if ( strcmp ( $str, $value) == 0 ) {
2236 return htmlspecialchars( trim( $show ) );
2244 * languages like Chinese need to be segmented in order for the diff
2247 * @param $text String
2250 function segmentForDiff( $text ) {
2255 * and unsegment to show the result
2257 * @param $text String
2260 function unsegmentForDiff( $text ) {
2264 # convert text to all supported variants
2265 function autoConvertToAllVariants($text) {
2266 return $this->mConverter
->autoConvertToAllVariants($text);
2269 # convert text to different variants of a language.
2270 function convert( $text, $isTitle = false) {
2271 return $this->mConverter
->convert($text, $isTitle);
2274 # Convert text from within Parser
2275 function parserConvert( $text, &$parser ) {
2276 return $this->mConverter
->parserConvert( $text, $parser );
2279 # Check if this is a language with variants
2280 function hasVariants(){
2281 return sizeof($this->getVariants())>1;
2284 # Put custom tags (e.g. -{ }-) around math to prevent conversion
2285 function armourMath($text){
2286 return $this->mConverter
->armourMath($text);
2291 * Perform output conversion on a string, and encode for safe HTML output.
2292 * @param $text String
2293 * @param $isTitle Bool -- wtf?
2295 * @todo this should get integrated somewhere sane
2297 function convertHtml( $text, $isTitle = false ) {
2298 return htmlspecialchars( $this->convert( $text, $isTitle ) );
2301 function convertCategoryKey( $key ) {
2302 return $this->mConverter
->convertCategoryKey( $key );
2306 * get the list of variants supported by this langauge
2307 * see sample implementation in LanguageZh.php
2309 * @return array an array of language codes
2311 function getVariants() {
2312 return $this->mConverter
->getVariants();
2316 function getPreferredVariant( $fromUser = true ) {
2317 return $this->mConverter
->getPreferredVariant( $fromUser );
2321 * if a language supports multiple variants, it is
2322 * possible that non-existing link in one variant
2323 * actually exists in another variant. this function
2324 * tries to find it. See e.g. LanguageZh.php
2326 * @param $link String: the name of the link
2327 * @param $nt Mixed: the title object of the link
2328 * @param boolean $ignoreOtherCond: to disable other conditions when
2329 * we need to transclude a template or update a category's link
2330 * @return null the input parameters may be modified upon return
2332 function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
2333 $this->mConverter
->findVariantLink( $link, $nt, $ignoreOtherCond );
2337 * If a language supports multiple variants, converts text
2338 * into an array of all possible variants of the text:
2339 * 'variant' => text in that variant
2341 function convertLinkToAllVariants($text){
2342 return $this->mConverter
->convertLinkToAllVariants($text);
2347 * returns language specific options used by User::getPageRenderHash()
2348 * for example, the preferred language variant
2352 function getExtraHashOptions() {
2353 return $this->mConverter
->getExtraHashOptions();
2357 * for languages that support multiple variants, the title of an
2358 * article may be displayed differently in different variants. this
2359 * function returns the apporiate title defined in the body of the article.
2363 function getParsedTitle() {
2364 return $this->mConverter
->getParsedTitle();
2368 * Enclose a string with the "no conversion" tag. This is used by
2369 * various functions in the Parser
2371 * @param $text String: text to be tagged for no conversion
2373 * @return string the tagged text
2375 function markNoConversion( $text, $noParse=false ) {
2376 return $this->mConverter
->markNoConversion( $text, $noParse );
2380 * A regular expression to match legal word-trailing characters
2381 * which should be merged onto a link of the form [[foo]]bar.
2385 function linkTrail() {
2386 return self
::$dataCache->getItem( $this->mCode
, 'linkTrail' );
2389 function getLangObj() {
2394 * Get the RFC 3066 code for this language object
2396 function getCode() {
2397 return $this->mCode
;
2400 function setCode( $code ) {
2401 $this->mCode
= $code;
2404 static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
2405 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
2408 static function getMessagesFileName( $code ) {
2410 return self
::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
2413 static function getClassFileName( $code ) {
2415 return self
::getFileName( "$IP/languages/classes/Language", $code, '.php' );
2419 * Get the fallback for a given language
2421 static function getFallbackFor( $code ) {
2422 if ( $code === 'en' ) {
2426 return self
::getLocalisationCache()->getItem( $code, 'fallback' );
2431 * Get all messages for a given language
2432 * WARNING: this may take a long time
2434 static function getMessagesFor( $code ) {
2435 return self
::getLocalisationCache()->getItem( $code, 'messages' );
2439 * Get a message for a given language
2441 static function getMessageFor( $key, $code ) {
2442 return self
::getLocalisationCache()->getSubitem( $code, 'messages', $key );
2445 function fixVariableInNamespace( $talk ) {
2446 if ( strpos( $talk, '$1' ) === false ) return $talk;
2448 global $wgMetaNamespace;
2449 $talk = str_replace( '$1', $wgMetaNamespace, $talk );
2451 # Allow grammar transformations
2452 # Allowing full message-style parsing would make simple requests
2453 # such as action=raw much more expensive than they need to be.
2454 # This will hopefully cover most cases.
2455 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
2456 array( &$this, 'replaceGrammarInNamespace' ), $talk );
2457 return str_replace( ' ', '_', $talk );
2460 function replaceGrammarInNamespace( $m ) {
2461 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
2464 static function getCaseMaps() {
2465 static $wikiUpperChars, $wikiLowerChars;
2466 if ( isset( $wikiUpperChars ) ) {
2467 return array( $wikiUpperChars, $wikiLowerChars );
2470 wfProfileIn( __METHOD__
);
2471 $arr = wfGetPrecompiledData( 'Utf8Case.ser' );
2472 if ( $arr === false ) {
2473 throw new MWException(
2474 "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" );
2477 wfProfileOut( __METHOD__
);
2478 return array( $wikiUpperChars, $wikiLowerChars );
2481 function formatTimePeriod( $seconds ) {
2482 if ( $seconds < 10 ) {
2483 return $this->formatNum( sprintf( "%.1f", $seconds ) ) . wfMsg( 'seconds-abbrev' );
2484 } elseif ( $seconds < 60 ) {
2485 return $this->formatNum( round( $seconds ) ) . wfMsg( 'seconds-abbrev' );
2486 } elseif ( $seconds < 3600 ) {
2487 return $this->formatNum( floor( $seconds / 60 ) ) . wfMsg( 'minutes-abbrev' ) .
2488 $this->formatNum( round( fmod( $seconds, 60 ) ) ) . wfMsg( 'seconds-abbrev' );
2490 $hours = floor( $seconds / 3600 );
2491 $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
2492 $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
2493 return $this->formatNum( $hours ) . wfMsg( 'hours-abbrev' ) .
2494 $this->formatNum( $minutes ) . wfMsg( 'minutes-abbrev' ) .
2495 $this->formatNum( $secondsPart ) . wfMsg( 'seconds-abbrev' );
2499 function formatBitrate( $bps ) {
2500 $units = array( 'bps', 'kbps', 'Mbps', 'Gbps' );
2502 return $this->formatNum( $bps ) . $units[0];
2504 $unitIndex = floor( log10( $bps ) / 3 );
2505 $mantissa = $bps / pow( 1000, $unitIndex );
2506 if ( $mantissa < 10 ) {
2507 $mantissa = round( $mantissa, 1 );
2509 $mantissa = round( $mantissa );
2511 return $this->formatNum( $mantissa ) . $units[$unitIndex];
2515 * Format a size in bytes for output, using an appropriate
2516 * unit (B, KB, MB or GB) according to the magnitude in question
2518 * @param $size Size to format
2519 * @return string Plain text (not HTML)
2521 function formatSize( $size ) {
2522 // For small sizes no decimal places necessary
2524 if( $size > 1024 ) {
2525 $size = $size / 1024;
2526 if( $size > 1024 ) {
2527 $size = $size / 1024;
2528 // For MB and bigger two decimal places are smarter
2530 if( $size > 1024 ) {
2531 $size = $size / 1024;
2532 $msg = 'size-gigabytes';
2534 $msg = 'size-megabytes';
2537 $msg = 'size-kilobytes';
2540 $msg = 'size-bytes';
2542 $size = round( $size, $round );
2543 $text = $this->getMessageFromDB( $msg );
2544 return str_replace( '$1', $this->formatNum( $size ), $text );